#include "mpHashUtils.h"

#include <iomanip>
#include <sstream>
#include <fstream>

#if defined(MP_HASH_USE_BOOST)
#include <boost/algorithm/hex.hpp>
#include <boost/uuid/detail/md5.hpp>
#include <boost/uuid/detail/sha1.hpp>
#elif 1
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/md5.h>
#endif //MP_HASH_USE_BOOST


#include <boost/lexical_cast.hpp>
#include "mpCRC64.h"

//https://pragmaticjoe.gitlab.io/posts/2015-02-09-how-to-generate-a-sha1-hash-in-c/

namespace MP{
namespace HashUtils{

//private member
namespace{

//const unsigned char map[] = "0123456789abcdef";

#if defined(MP_HASH_USE_BOOST)

#elif 1
void _toHexString(const unsigned char* md, int len, std::string & out){


    out.clear();

    std::ostringstream buf;
    for(int i = 0; i < len; ++i) {
        buf << std::setiosflags(std::ios::uppercase) << std::hex << ((md[i] & 0x0000000F0) >> 4);
        buf << std::setiosflags(std::ios::uppercase) << std::hex << (md[i] & 0x00000000F);
    }

    out = buf.str();

//    for (size_t i = 0; i < len; ++i) {
//        out += map[md5[i] / 16];
//        out += map[md5[i] % 16];
//    }
}




#endif //MP_HASH_USE_BOOST

} //private member

///**
// * @brief 将十六进制字符串转换为二进制值
// * @param str_hex
// * @param str_bin
// * @return
// */
//bool hexStringToBin(const std::string& str_hex, std::string & str_bin){
//    str_bin.clear();

//    if (str_hex.size() % 2 != 0) {
//        return false;
//    }

//    str_bin.resize(str_hex.size() / 2);

//    for (size_t i = 0; i < str_hex.size(); i++) {
//        uint8_t cTmp = 0;
//        for (size_t j = 0; j < 2; j++) {
//            char cCur = str_hex[2 * i + j];
//            if (cCur >= '0' && cCur <= '9') {
//                cTmp = (cTmp << 4) + (cCur - '0');
//            } else if (cCur >= 'a' && cCur <= 'f') {
//                cTmp = (cTmp << 4) + (cCur - 'a' + 10);
//            } else if (cCur >= 'A' && cCur <= 'F') {
//                cTmp = (cTmp << 4) + (cCur - 'A' + 10);
//            } else {
//                return false;
//            }
//        }
//        str_bin[i] = cTmp;
//    }
//    return true;
//}

/**
 * @brief 计算文本的hmacSha1值
 * @param text
 * @param key
 * @param str_hmacSha1
 * @return
 */
bool text_hmacSha1_hex(const std::string & text, const std::string & key, std::string & str_hmacSha1){

    if(text.empty()){
        return false;
    }

    if(key.empty()){
        return false;
    }

    return bytes_hmacSha1_hex((const unsigned char *)text.c_str(), text.length(),(const unsigned char *)key.c_str(), key.length(),str_hmacSha1);
}

/**
 * @brief 计算文本的MD5
 * @param text 文本内容（输入）
 * @param str_md5 MD5（输出）
 * @return 是否计算成功
 */
bool text_md5_hex(const std::string & text, std::string & str_md5){
    if(text.empty()){
        return false;
    }
    return bytes_md5_hex((const unsigned char *)text.c_str(), text.length(),str_md5);

//    unsigned char md5[MD5_DIGEST_LENGTH] = {0};
//    MD5((const unsigned char *)text.c_str(), text.length(), md5);
//    _toHexString(md5,MD5_DIGEST_LENGTH,str_md5);
//    return true;

}

/**
 * @brief 计算文本的SHA1
 * @param text 文本内容（输入）
 * @param str_sha1 SHA1（输出）
 * @return 是否计算成功
 */
bool text_sha1_hex(const std::string & text, std::string & str_sha1){

    if(text.empty()){
        return false;
    }
    return bytes_sha1_hex((const unsigned char *)text.c_str(), text.length(),str_sha1);
//    unsigned char sha1[SHA_DIGEST_LENGTH] = {0};
//    SHA1((const unsigned char *)text.c_str(), text.length(), sha1);
//    _toHexString(sha1,SHA_DIGEST_LENGTH,str_sha1);
//    return true;
}

#if defined(MP_HASH_USE_BOOST)


bool bytes_md5_hex(const unsigned char *data, size_t length, std::string & str_md5){
    if(length<= 0){
        return false;
    }

    boost::uuids::detail::md5 boost_md5;
    boost_md5.process_bytes(data, length);
    boost::uuids::detail::md5::digest_type digest;
    boost_md5.get_digest(digest);
    const auto char_digest = reinterpret_cast<const char*>(&digest);
    str_md5.clear();
    boost::algorithm::hex(char_digest,char_digest+sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(str_md5));

    return true;
}

bool bytes_sha1_hex(const unsigned char *data, size_t length, std::string & str_sha1){
    if(length<= 0){
        return false;
    }

    char hash[20];
    boost::uuids::detail::sha1 boost_sha1;
    boost_sha1.process_bytes(data, length);
    boost::uuids::detail::sha1::digest_type digest;
    boost_sha1.get_digest(digest);
    for(int i = 0; i < 5; ++i) {
        const char *tmp = reinterpret_cast<char*>(digest);
        hash[i*4] = tmp[i*4+3];
        hash[i*4+1] = tmp[i*4+2];
        hash[i*4+2] = tmp[i*4+1];
        hash[i*4+3] = tmp[i*4];
    }

    str_sha1.clear();
    std::ostringstream buf;
    for(int i = 0; i < 20; ++i) {
        buf << std::setiosflags(std::ios::uppercase) << std::hex << ((hash[i] & 0x0000000F0) >> 4);
        buf << std::setiosflags(std::ios::uppercase) << std::hex << (hash[i] & 0x00000000F);
    }

    str_sha1 = buf.str();

    return true;
}


bool bytes_hmacSha1_hex(const unsigned char *data, size_t data_length, const unsigned char *key,size_t key_length, std::string & str_hmacSha1){
    if(data_length<= 0){
        return false;
    }

    if(key_length<= 0){
        return false;
    }
    return false;
}

/**
 * @brief 计算文件的MD5
 * @param text 文件路径（输入）
 * @param str_md5 MD5（输出）
 * @return 是否计算成功
 */
bool file_md5_hex(const std::string & filePath, std::string & str_md5){

    str_md5.clear();

    std::ifstream file(filePath, std::ios::in | std::ios::binary);
    if (!file)
    {
        return false;
    }

    boost::uuids::detail::md5 boost_md5;

    char buf[1024 * 16];
    while (file.good()) {
        file.read(buf, sizeof(buf));
        boost_md5.process_bytes(buf, file.gcount());
    }

    boost::uuids::detail::md5::digest_type digest;
    boost_md5.get_digest(digest);


    const auto char_digest = reinterpret_cast<const char*>(&digest);
    boost::algorithm::hex(char_digest,char_digest+sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(str_md5));

    file.close();

    return true;
}

/**
 * @brief 计算文本的SHA1
 * @param text 文件路径（输入）
 * @param str_sha1 SHA1（输出）
 * @return 是否计算成功
 */
bool file_sha1_hex(const std::string & filePath, std::string & str_sha1){

    str_sha1.clear();

    std::ifstream file(filePath, std::ios::in | std::ios::binary);
    if (!file)
    {
        return false;
    }

    boost::uuids::detail::sha1 boost_sha1;

    char buf[1024 * 16];
    while (file.good()) {
        file.read(buf, sizeof(buf));
        boost_sha1.process_bytes(buf, file.gcount());
    }

    char hash[20];
    boost::uuids::detail::sha1::digest_type digest;
    boost_sha1.get_digest(digest);
    for(int i = 0; i < 5; ++i) {
        const char *tmp = reinterpret_cast<char*>(digest);
        hash[i*4] = tmp[i*4+3];
        hash[i*4+1] = tmp[i*4+2];
        hash[i*4+2] = tmp[i*4+1];
        hash[i*4+3] = tmp[i*4];
    }

    str_sha1.clear();
    std::ostringstream bufResult;
    for(int i = 0; i < 20; ++i) {
        bufResult << std::setiosflags(std::ios::uppercase) << std::hex << ((hash[i] & 0x0000000F0) >> 4);
        bufResult << std::setiosflags(std::ios::uppercase) << std::hex << (hash[i] & 0x00000000F);
    }

    str_sha1 = bufResult.str();

    file.close();

    return true;
}

#elif 1

bool bytes_md5_hex(const unsigned char *data, size_t length, std::string & str_md5){
    if(length<= 0){
        return false;
    }

    unsigned char md5[MD5_DIGEST_LENGTH] = {0};
    MD5(data, length, md5);

    _toHexString(md5,MD5_DIGEST_LENGTH,str_md5);

    return true;
}

bool bytes_sha1_hex(const unsigned char *data, size_t length, std::string & str_sha1){
    if(length<= 0){
        return false;
    }

    unsigned char sha1[SHA_DIGEST_LENGTH] = {0};
    SHA1(data, length, sha1);

    _toHexString(sha1,SHA_DIGEST_LENGTH,str_sha1);

    return true;
}

bool bytes_hmacSha1_hex(const unsigned char *data, size_t data_length, const unsigned char *key,size_t key_length, std::string & str_hmacSha1){

    //https://blog.csdn.net/mijichui2153/article/details/104741460

    if(data_length<= 0){
        return false;
    }

    if(key_length<= 0){
        return false;
    }

    unsigned int output_len = 20;
    unsigned char* hmacSha1;
    hmacSha1 = (unsigned char*)malloc(EVP_MAX_MD_SIZE);

    HMAC_CTX ctx;
    HMAC_CTX_init(&ctx);

    // You may use other hash engines. e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
    HMAC_Init_ex(&ctx, key, key_length, EVP_sha1(), NULL);
    HMAC_Update(&ctx, data, data_length);
    HMAC_Final(&ctx, hmacSha1, &output_len);
    HMAC_CTX_cleanup(&ctx);

    _toHexString(hmacSha1,output_len,str_hmacSha1);

    if(hmacSha1) {
        free(hmacSha1);
    }

    return true;
}

/**
 * @brief 计算文件的MD5
 * @param text 文件路径（输入）
 * @param str_md5 MD5（输出）
 * @return 是否计算成功
 */
bool file_md5_hex(const std::string & filePath, std::string & str_md5){

    return file_md5_hex(filePath,0,0,str_md5);
//    std::ifstream file(filePath, std::ios::in | std::ios::binary);
//    if (!file)
//    {
//        return false;
//    }


//    char buf[1024 * 16];
//    unsigned char md5[MD5_DIGEST_LENGTH] = {0};

//    MD5_CTX ctx;
//    MD5_Init(&ctx); //初始化MD5上下文结构

//    while (file.good()) {
//        file.read(buf, sizeof(buf));
//        MD5_Update(&ctx, buf, file.gcount());
//    }

//    MD5_Final(md5, &ctx); //产生最终的MD5数据
//    file.close();

//    _toHexString(md5,MD5_DIGEST_LENGTH,str_md5);


//    return true;
}

bool file_md5_hex(const std::string & filePath,
                  std::size_t _fileOffset,
                  std::size_t _sendContentSize,
                  std::string & str_md5){

    std::ifstream file(filePath, std::ios::in | std::ios::binary);
    if (!file)
    {
        return false;
    }

    // 计算文件大小
    file.seekg(0, std::ios::end);
    size_t file_size = file.tellg();

    if(file_size > _fileOffset){

        if(_sendContentSize == 0){
            _sendContentSize = file_size - _fileOffset;
        }

        std::streamsize buf_size = 16384;
        char buf[buf_size];
        unsigned char md5[MD5_DIGEST_LENGTH] = {0};

        MD5_CTX ctx;
        MD5_Init(&ctx); //初始化MD5上下文结构

        file.seekg(_fileOffset,std::ios::beg);
        std::streamsize unreadSize = boost::lexical_cast<std::streamsize>(_sendContentSize) ;

        while (file.good() && unreadSize > 0) {

            //如果 sendContentSize 则认为是需要从 fileOffset 开始读取，一直读到文件末尾
            file.read(buf, (unreadSize > buf_size ? buf_size : unreadSize));
            MD5_Update(&ctx, buf, file.gcount());

            unreadSize = unreadSize - file.gcount();
        }

        MD5_Final(md5, &ctx); //产生最终的MD5数据

        _toHexString(md5,MD5_DIGEST_LENGTH,str_md5);
    }

    file.close();
    return true;
}

/**
 * @brief 计算文本的SHA1
 * @param text 文件路径（输入）
 * @param str_sha1 SHA1（输出）
 * @return 是否计算成功
 */
bool file_sha1_hex(const std::string & filePath, std::string & str_sha1){

    std::ifstream file(filePath, std::ios::in | std::ios::binary);
    if (!file)
    {
        return false;
    }

    char buf[1024 * 16];
    unsigned char sha1[SHA_DIGEST_LENGTH] = {0};

    SHA_CTX ctx;
    SHA1_Init(&ctx); //初始化MD5上下文结构

    while (file.good()) {
        file.read(buf, sizeof(buf));
        SHA1_Update(&ctx, buf, file.gcount());
    }

    SHA1_Final(sha1, &ctx); //产生最终的MD5数据
    file.close();

    _toHexString(sha1,SHA_DIGEST_LENGTH,str_sha1);

    return true;
}

/**
 * @brief 计算文件的CRC64值
 * @param filePath
 * @return
 */
uint64_t file_crc64(const std::string & filePath){

    uint64_t tmp = 0;

    std::ifstream file(filePath, std::ios::in | std::ios::binary);
    if (file)
    {
        const static int buffer_size = 2048;
        char buffer[buffer_size];
        while (file.good()) {
            file.read(buffer, buffer_size);
            auto bytes_read = file.gcount();
            if (bytes_read > 0) {
                tmp = MP::CRC64::CalcCRC(tmp, static_cast<void*>(buffer),
                                         static_cast<size_t>(bytes_read));
            }
        }
        file.close();
    }

    return tmp;

}

#endif //MP_HASH_USE_BOOST

}   //END HashUtils
}   //END MP











